﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml.Serialization;

namespace d39
{
    public enum DnDVersion { V35, V40 };

    [Serializable]
    [XmlRoot("grid")]
    public sealed class Grid
    {
        public Grid()
        {
            Width = 20;
            Height = 20;
            PixelSize = 10;
            Players = new List<Player>();
            TerrainTypes = new List<TerrainType>();
            WallTypes = new List<WallType>();
            EffectTypes = new List<EffectType>();
            DnDVersion = DnDVersion.V35;
            BackgroundColor = Color.FromArgb(255, 255, 255);
            GridLineColor = Color.FromArgb(200, 200, 200);
            KnownLayers = new LayerSet(1);
        }

        [XmlAttribute("name")]
        public string Name { get; set; }

        [XmlAttribute("width")]
        public int Width { get; set; }

        [XmlAttribute("height")]
        public int Height { get; set; }

        [XmlAttribute("pixel-size")]
        public int PixelSize { get; set; }

        [XmlElement("player")]
        public List<Player> Players { get; set; }

        [XmlElement("terrain")]
        public List<TerrainType> TerrainTypes { get; set; }

        [XmlElement("walls")]
        public List<WallType> WallTypes { get; set; }

        [XmlElement("effect")]
        public List<EffectType> EffectTypes { get; set; }

        [XmlAttribute("dnd-version")]
        public DnDVersion DnDVersion { get; set; }

        [XmlIgnore]
        public Color BackgroundColor { get; set; }

        [XmlAttribute("background")]
        public string BackgroundColorString
        {
            get { return ColorTranslator.ToHtml(BackgroundColor); }
            set { BackgroundColor = ColorTranslator.FromHtml(value); }
        }

        [XmlIgnore]
        public Color GridLineColor { get; set; }

        [XmlAttribute("grid-lines")]
        public string GridLineColorString
        {
            get { return ColorTranslator.ToHtml(GridLineColor); }
            set { GridLineColor = ColorTranslator.FromHtml(value); }
        }

        [XmlIgnore]
        public bool RevealUnknown { get; set; }

        [XmlIgnore]
        public LayerSet KnownLayers { get; set; }

        [XmlAttribute("known-layers")]
        public string KnownLayersString
        {
            get { return KnownLayers.ToXmlString(); }
            set { KnownLayers = LayerSet.FromXmlString(value); }
        }

        [XmlIgnore]
        public LayerSet AllLayers
        {
            get
            {
                var layers = KnownLayers;
                layers |= (from wt in WallTypes
                           from segment in wt.Segments
                           select segment.Layers).Union();
                layers |= (from tt in TerrainTypes
                           from segment in tt.Segments
                           select segment.Layers).Union();
                layers |= (from p in Players
                           from pm in p.Positions
                           select pm.Layers).Union();
                return layers;
            }
        }

        [XmlIgnore]
        public IEnumerable<Player> KnownPlayers { get { return Players.Where(p => !p.IsUnknown); } }

        [XmlIgnore]
        public IEnumerable<Player> RevealedPlayers { get { return RevealUnknown ? Players : KnownPlayers; } }

        [XmlIgnore]
        public LayerSet RevealedLayers { get { return RevealUnknown ? AllLayers : KnownLayers; } }

        public int GetImageWidth(UrlTweaks tweaks)
        {
            var width = tweaks.ForceSizeToZero ? 0 : Width;
            var pixelSize = tweaks.PixelSizeOverride ?? PixelSize;

            var gridWidth = width * pixelSize + 1;
            if (tweaks.IncludeLegendInImage)
            {
                using (var font = new Font(FontFamily.GenericSansSerif, pixelSize + 2, GraphicsUnit.Pixel))
                {
                    using (var smallFont = new Font(FontFamily.GenericSansSerif, pixelSize * 0.8f + 2, GraphicsUnit.Pixel))
                    {
                        var players = (tweaks.IncludeUnknown && (tweaks.RevealUnknown ?? RevealUnknown)) ? Players : KnownPlayers;
                        gridWidth += players.Max(p => pixelSize * (2 + p.SizeInSquares)) +
                            players.Max(p => Math.Max(Math.Max(
                            TextRenderer.MeasureText("" + p.NameAndPositions, font).Width,
                            TextRenderer.MeasureText("" + p.Description, smallFont).Width),
                            p.GetRowsOfPositions(this, tweaks).Max(s => TextRenderer.MeasureText(s, smallFont).Width, 0)), 0);
                    }
                }
            }
            return gridWidth;
        }

        public int GetImageHeight(UrlTweaks tweaks)
        {
            var height = tweaks.ForceSizeToZero ? 0 : Height;
            var pixelSize = tweaks.PixelSizeOverride ?? PixelSize;

            if (tweaks.IncludeLegendInImage)
            {
                var players = (tweaks.IncludeUnknown && (tweaks.RevealUnknown ?? RevealUnknown)) ? Players : KnownPlayers;
                return Math.Max(height, players.Sum(p => p.SizeInSquares + 1 + (string.IsNullOrWhiteSpace(p.Description) ? 0 : 1) + p.GetRowsOfPositions(this, tweaks).Count())) * pixelSize + 1;
            }
            else
            {
                return height * pixelSize + 1;
            }
        }

        [XmlIgnore]
        public IEnumerable<PositionMove> Moves
        {
            get
            {
                return from p in Players
                       from pm in p.Positions
                       where pm.Moves.Any()
                       select pm;
            }
        }

        [XmlIgnore]
        public IEnumerable<PositionMove> KnownMoves { get { return Moves.Where(m => !m.IsUnknown); } }

        [XmlIgnore]
        public IEnumerable<PositionMove> RevealedMoves { get { return RevealUnknown ? Moves : KnownMoves; } }

        public static Grid FromQueryString(NameValueCollection queryString)
        {
            var grid = new Grid();
            if (queryString["v"] != null)
            {
                grid.DnDVersion = (DnDVersion)Enum.Parse(typeof(DnDVersion), "V" + queryString["v"]);
            }
            if (queryString["ru"] != null)
            {
                grid.RevealUnknown = true;
            }
            if (queryString["px"] != null) grid.PixelSize = int.Parse(queryString["px"]);
            if (queryString["bg"] != null) grid.BackgroundColor = ColorTranslator.FromHtml("#" + queryString["bg"]);
            if (queryString["gl"] != null) grid.GridLineColor = ColorTranslator.FromHtml("#" + queryString["gl"]);
            if (queryString["s"] != null)
            {
                int end;
                grid.Width = Base26.ParseInt(queryString["s"], 0, out end);
                grid.Height = Base26.ParseInt(queryString["s"], end, out end);
            };
            grid.Name = string.IsNullOrEmpty(queryString["n"]) ? null : queryString["n"];

            if (queryString["p"] != null)
            {
                foreach (var p in queryString["p"].Split('\n'))
                {
                    grid.Players.Add(Player.FromString(p.Trim(), grid.DnDVersion));
                }
            }
            if (queryString["t"] != null)
            {
                foreach (var t in queryString["t"].Split('\n'))
                {
                    grid.TerrainTypes.Add(TerrainType.FromString(t.Trim()));
                }
            }
            if (queryString["w"] != null)
            {
                foreach (var w in queryString["w"].Split('\n'))
                {
                    grid.WallTypes.Add(WallType.FromString(w.Trim()));
                }
            }
            if (queryString["e"] != null)
            {
                foreach (var e in queryString["e"].Split('\n'))
                {
                    grid.EffectTypes.Add(EffectType.FromString(e.Trim()));
                }
            }
            return grid;
        }

        public string ToQueryString(UrlTweaks tweaks)
        {
            var result = "";
            if (DnDVersion != DnDVersion.V35) result += "&v=" + DnDVersion.ToString().Substring(1);
            
            var pixelSize = tweaks.PixelSizeOverride ?? PixelSize;
            if (pixelSize != 10) result += "&px=" + pixelSize.ToString();

            if (tweaks.ForceSizeToZero)
            {
                result += "&s=00";
            }
            else if (Width != 20 || Height != 20)
            {
                result += "&s=" + Base26.IntToString(Width) + Base26.IntToString(Height);
            }
            if (tweaks.IncludeDescriptions && Name != null) result += "&n=" + HttpUtility.UrlEncode(Name);
            if (tweaks.RevealUnknown ?? RevealUnknown) result += "&ru=";
            if (tweaks.IncludeBackgroundColor && BackgroundColor != Color.FromArgb(255, 255, 255)) result += "&bg=" + ColorTranslator.ToHtml(BackgroundColor).Substring(1);
            if (tweaks.IncludeGridLineColor && GridLineColor != Color.FromArgb(200, 200, 200)) result += "&gl=" + ColorTranslator.ToHtml(GridLineColor).Substring(1);
            if (tweaks.IncludeLegendInImage) result += "&l=1";
            if (tweaks.IncludePlayers && Players.Any())
            {
                var includedPlayers = from p in Players where tweaks.IncludeUnknown || !p.IsUnknown select p.ToString(tweaks);
                result += "&p=" + HttpUtility.UrlEncode(string.Join("\n", includedPlayers));
            }
            if (tweaks.IncludeWalls)
            {
                var includedWalls = from w in WallTypes where tweaks.IncludeTransparentItems || w.Opacity > 0 select w.ToString(tweaks);
                if (includedWalls.Any()) result += "&w=" + HttpUtility.UrlEncode(string.Join("\n", includedWalls));
            }
            if (tweaks.IncludeTerrain)
            {
                var includedTerrain = from t in TerrainTypes where tweaks.IncludeTransparentItems || t.Opacity > 0 select t.ToString(tweaks);
                if (includedTerrain.Any()) result += "&t=" + HttpUtility.UrlEncode(string.Join("\n", includedTerrain));
            }
            if (tweaks.IncludeEffects)
            {
                var includedEffects = from e in EffectTypes where tweaks.IncludeTransparentItems || e.Opacity > 0 || e.PerimeterThickness > 0 select e.ToString(tweaks);
                if (includedEffects.Any()) result += "&e=" + HttpUtility.UrlEncode(string.Join("\n", includedEffects));
            }
            return result == "" ? "" : "?" + result.Substring(1);
        }
        private static string portStr(Uri url)
        {
            return url.IsDefaultPort ? "" : ":" + url.Port;
        }
        public string GetPageUrl(string pageName, UrlTweaks tweaks)
        {
            var url = HttpContext.Current.Request.Url;
            return url.Scheme + "://" + url.Host + portStr(url) + "/" + pageName + ".aspx" + ToQueryString(tweaks);
        }
        public string GetImageUrl(UrlTweaks tweaks)
        {
            var url = HttpContext.Current.Request.Url;
            return url.Scheme + "://" + url.Host + portStr(url) + "/GridImage.aspx" + ToQueryString(new UrlTweaks(tweaks)
            {
                IncludeTransparentItems = false,
                IncludeUnknown = tweaks.RevealUnknown ?? RevealUnknown,
            });
        }

        public float HalfCoordinateToPixel(int coord)
        {
            return (coord - 2) * PixelSize / 2.0f;
        }
        public PointF CoordinateToPoint(HalfCoordinate coordinate)
        {
            return new PointF(HalfCoordinateToPixel(coordinate.DoubleX), HalfCoordinateToPixel(coordinate.DoubleY));
        }
        public SizeF GridSizeToPixelSize(int width, int height)
        {
            return new SizeF(width * PixelSize, height * PixelSize);
        }

        public void ShiftAllCoordinates(Vector dir)
        {
            foreach (var player in Players)
            {
                foreach (var pm in player.Positions)
                {
                    pm.Position += dir;
                }
            }
            foreach (var terrain in TerrainTypes)
            {
                foreach (var segment in terrain.Segments)
                {
                    segment.Shift(dir);
                }
            }
            foreach (var wall in WallTypes)
            {
                foreach (var segment in wall.Segments)
                {
                    segment.Shift(dir);
                }
            }
            foreach (var effect in EffectTypes)
            {
                foreach (var instance in effect.Instances)
                {
                    instance.StartCorner += dir;
                }
            }
        }
    }
}